﻿namespace IBTrader.Modules
{
    using IBTrader.Modules.Configuration;
    using NLog;
    using System;
    using System.Collections.Generic;
    using System.Drawing;
    using System.Globalization;
    using System.Linq;
    using System.Reflection;

    class Creator
    {
        private static readonly Logger logger = LogManager.GetCurrentClassLogger();
        internal static Dictionary<string, object> Create(Component settings, object core)
        {
            Dictionary<string, object> members = new Dictionary<string, object>();
            W.X(logger, () =>
            {
                members.Add("core", core);
                CreateObjects(settings, members);
                AttachEvents(settings, members);
            });
            return members;
        }
        internal static T Select<T>(IEnumerable<KeyValuePair<string, object>> objects, string key)
        {
            return Select<T>(objects.Where(item => item.Key.Equals(key)));
        }
        internal static T Select<T>(IEnumerable<KeyValuePair<string, object>> objects)
        {
            return objects.Where(item => item.Value is T).Select(item => item.Value).OfType<T>().FirstOrDefault();
        }
        private static void CreateObjects(Component settings, Dictionary<string, object> members)
        {
            foreach (ObjectElement obj in settings.Objects)
            {
                var type = Type.GetType(obj.Type);

                if (type == null)
                {
                    logger.Error("Configuration Error in {0} section. Type {1} not found.", settings.SectionInformation.Name, obj.Type);
                    continue;
                }

                var parms = obj.Params.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
                var args = parms.Select(k => k.Trim()).Select(k => members.ContainsKey(k) ? members[k] : k).ToList();

                var inst = Activator.CreateInstance(type,
                    BindingFlags.CreateInstance |
                    BindingFlags.Public |
                    BindingFlags.Instance |
                    BindingFlags.OptionalParamBinding, new MyBinder(), args.ToArray(), CultureInfo.InvariantCulture);


                members.Add(obj.Name, inst);
                //members.Add(obj.Name, Activator.CreateInstance(type, args.ToArray()));
            }
        }
        private static void AttachEvents(Component settings, Dictionary<string, object> members)
        {
            foreach (EventElement dynamic in settings.Events)
            {
                if (!members.ContainsKey(dynamic.Source) || !members.ContainsKey(dynamic.Target))
                {
                    logger.Warn("Configuration Error in {0} section. Event source {1} or target {2} not found.", settings.SectionInformation.Name, dynamic.Source, dynamic.Target);
                    continue;
                }

                var source = members[dynamic.Source];
                var target = members[dynamic.Target];
                var eventInfo = source.GetType().GetEvent(dynamic.EventName);

                if (eventInfo == null)
                {
                    logger.Error("Configuration Error in {0} section. Source [{1}] doesn't contain event [{2}].", settings.SectionInformation.Name, dynamic.Source, dynamic.EventName);
                    continue;
                }

                var type = eventInfo.EventHandlerType;
                var eventMethod = type.GetMethod("Invoke");
                var eventTypes = eventMethod.GetParameters().Select(p => p.ParameterType).ToArray();
                var method = target.GetType().GetMethod(dynamic.Method, eventTypes);

                if (method == null)
                {
                    logger.Error("Configuration Error in {0} section. Method [{1}] for target [{2}] not found.", settings.SectionInformation.Name, dynamic.Method, target);
                    return;
                }

                if (dynamic.Wrap)
                {
                    AddWrapper(eventTypes[1], ref target, ref method, dynamic.Method, settings.SectionInformation.Name);
                }

                var handler = Delegate.CreateDelegate(type, target, method);
                eventInfo.AddEventHandler(source, handler);
            }
        }
        private static void AddWrapper(Type argumentType, ref object target, ref MethodInfo method, string methodName, string sectionName)
        {
            var type = typeof(Wrapper<>).MakeGenericType(argumentType);
            target = Activator.CreateInstance(type, target, method, methodName, sectionName);
            method = type.GetMethod("Do");
        }
        class Wrapper<T>
        {
            private static Logger logger = LogManager.GetCurrentClassLogger();
            private MethodInfo method;
            private object target;
            public Wrapper(object target, MethodInfo method, string methodName, string sectionName)
            {
                this.target = target;
                this.method = method;
                if (method == null)
                    logger.Error("Configuration Error in {0} section. Method [{1}] for target [{2}] not found", sectionName, methodName, target);
            }
            public void Do(object o, T e)
            {
                W.X(logger, () => method.Invoke(target, new object[] { o, e }));
            }
        }
        class MyBinder : Binder
        {
            private readonly Binder binder = Type.DefaultBinder;
            public override FieldInfo BindToField(BindingFlags bindingAttr, FieldInfo[] match, object value, CultureInfo culture)
            {
                throw new NotSupportedException();
            }

            public override MethodBase BindToMethod(BindingFlags bindingAttr, MethodBase[] match, ref object[] args, ParameterModifier[] modifiers, CultureInfo culture, string[] names, out object state)
            {
                if (match.Length == 1)
                {
                    var parameters = match.Single().GetParameters();
                    if (parameters.Length==args.Length)
                    {
                        for (int i = 0; i < args.Length; i++)
                        {
                            if (args[i] is string && (parameters[i].ParameterType.IsPrimitive || parameters[i].ParameterType.IsAssignableFrom(typeof(Color))))
                            {
                                if (parameters[i].ParameterType.IsPrimitive)
                                {
                                    args[i] = ChangeType(args[i], parameters[i].ParameterType, culture);
                                }
                                else if (parameters[i].ParameterType.IsAssignableFrom(typeof(Color)))
                                {
                                    args[i] = Color.FromName(args[i] as string);
                                }
                            }
                        }                        
                    }
                }
                return binder.BindToMethod(bindingAttr, match, ref args, modifiers, culture, names, out state);
            }

            public override object ChangeType(object value, Type type, CultureInfo culture)
            {
                return Convert.ChangeType(value, type, culture);
            }

            public override void ReorderArgumentArray(ref object[] args, object state)
            {
                throw new NotSupportedException();
            }

            public override MethodBase SelectMethod(BindingFlags bindingAttr, MethodBase[] match, Type[] types, ParameterModifier[] modifiers)
            {
                throw new NotSupportedException();
            }

            public override PropertyInfo SelectProperty(BindingFlags bindingAttr, PropertyInfo[] match, Type returnType, Type[] indexes, ParameterModifier[] modifiers)
            {
                throw new NotSupportedException();
            }
        }
    }
}